package com.jdy.processor;

import com.jdy.processor.annotation.Method;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.code.Symtab;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.Type.JCPrimitiveType;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.*;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.tools.Diagnostic.Kind;
import java.util.Set;

/**
 * Spring 框架学习
 * <p>
 * [Description]
 * <p>
 * 创建人 Dale 时间 2020/12/14 8:30
 */
@SupportedAnnotationTypes("com.jdy.processor.annotation.Method")
public class MethodProcessor extends AbstractProcessor {

    /**
     * 提供了待处理的抽象语法树
     */
    protected JavacTrees trees;

    /**
     * 封装了创建AST节点的一些方法
     */
    protected TreeMaker treeMaker;

    /**
     * 提供了创建标识符的方法
     */
    protected Names names;

    /**
     * 主要是用来在编译时期打log用的
     */
    private Messager messager;


    private JCPrimitiveType[] primitiveTypes;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        trees = JavacTrees.instance(processingEnv);
        messager = processingEnv.getMessager();

        messager.printMessage(Kind.NOTE, "init 初始化 Tree ： " + trees);

        if (processingEnv instanceof JavacProcessingEnvironment) {
            JavacProcessingEnvironment environment = (JavacProcessingEnvironment) processingEnv;

            Context context = environment.getContext();


            Symtab symtab = Symtab.instance(context);

            primitiveTypes = new JCPrimitiveType[]{symtab.byteType, symtab.intType, symtab.shortType, symtab.longType, symtab.floatType, symtab.doubleType, symtab.charType, symtab.booleanType};

            treeMaker = TreeMaker.instance(context);
            this.names = Names.instance(context);
        }
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        if (roundEnv.processingOver() || trees == null)
            return false;

        messager.printMessage(Kind.NOTE, "---------------");
        Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Method.class);
        for (Element element : elements) {
            trees.getTree(element).accept(new TreeTranslator() {
                @Override
                public void visitMethodDef(JCMethodDecl methodDecl) {
                    JCClassDecl classDecl = (JCClassDecl) trees.getTree(methodDecl.sym.owner);
                    ListBuffer<JCTree> buffer = new ListBuffer<>();

                    JCTree method = isNotEmptyMethod(null, methodDecl, true);

                    if (method != null)
                        buffer.add(method);

                    for (JCPrimitiveType type : primitiveTypes) {
                        method = isEmptyMethod(type, methodDecl);
                        if (method != null)
                            buffer.add(method);

                        method = isNotEmptyMethod(type, methodDecl);

                        if (method != null)
                            buffer.add(method);
                    }

                    classDecl.defs = classDecl.defs.appendList(buffer);
                }
            });
        }
        return true;
    }

    private JCTree isEmptyMethod(JCPrimitiveType type, JCMethodDecl methodDecl) {
        JCVariableDecl variableDecl = methodDecl.getParameters().head;
        Name name = names.fromString(variableDecl.name.toString());
        JCIdent param = treeMaker.Ident(name);
        Name isEmpty = names.fromString("isEmpty");

        JCBinary isEmptyBody = treeMaker.Binary(Tag.OR,
                treeMaker.Binary(Tag.EQ, param, treeMaker.Literal(TypeTag.BOT, null)),
                treeMaker.Binary(Tag.EQ, treeMaker.Select(param, names.fromString("length")), treeMaker.Literal(TypeTag.INT, 0)));

        JCVariableDecl variable = treeMaker.VarDef(variableDecl.getModifiers(),
                name, treeMaker.TypeArray(treeMaker.Type(type)), null);

        List<JCVariableDecl> parameters = List.of(variable);

        if (Utils.hasMethod(isEmpty, parameters, trees.getTree(methodDecl.sym.owner)))
            return null;

        return treeMaker.MethodDef(methodDecl.getModifiers(),
                isEmpty,
                (JCExpression) methodDecl.getReturnType(),
                List.nil(),
                parameters,
                List.nil(),
                treeMaker.Block(0, List.of(treeMaker.Return(isEmptyBody))),
                null);
    }

    private JCTree isNotEmptyMethod(Type type, JCMethodDecl methodDecl) {
        return isNotEmptyMethod(type, methodDecl, false);
    }

    private JCTree isNotEmptyMethod(Type type, JCMethodDecl methodDecl, boolean variable) {
        JCVariableDecl variableDecl = methodDecl.getParameters().head;

        Name isNotEmpty = names.fromString("isNotEmpty");
        Name isEmpty = names.fromString("isEmpty");

        Name name = names.fromString(variableDecl.name.toString());
        JCIdent param = treeMaker.Ident(name);


        JCUnary isNotEmptyBody = treeMaker.Unary(Tag.NOT, treeMaker.Apply(List.nil(), treeMaker.Ident(isEmpty), List.of(param)));

        List<JCTypeParameter> typeParameters = variable ? methodDecl.getTypeParameters() : List.nil();

        List<JCVariableDecl> parameters = variable ? methodDecl.getParameters() :
                List.of(treeMaker.VarDef(variableDecl.getModifiers(),
                        name, treeMaker.TypeArray(treeMaker.Type(type)), null));

        if (Utils.hasMethod(isNotEmpty, parameters, trees.getTree(methodDecl.sym.owner)))
            return null;

        return treeMaker.MethodDef(methodDecl.getModifiers(),
                isNotEmpty,
                (JCExpression) methodDecl.getReturnType(),
                typeParameters,
                parameters,
                methodDecl.getThrows(),
                treeMaker.Block(0, List.of(treeMaker.Return(isNotEmptyBody))),
                null);
    }


    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }
}
